/*	$Id: proto.c 7 2008-07-29 02:58:11Z phrakt $	*/
/*
 * Copyright (c) 2003 Jean-Francois Brousseau <jfb@openbsd.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Protocol switch table
 * ---------------------
 *
 * This code is the main switch table for all network protocols that are
 * supported by Packet Forge's network stack.
 */

#include <sys/types.h>

#include <err.h>
#include <stdio.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>

#include "log.h"
#include "proto.h"
#include "pforge.h"
#include "private.h"


#define PF_PROTO_GROWSIZE	4



struct pf_proto {
	int       pr_id;
	char      pr_name[PFPROTO_MAXNAMELEN];

	/* protocol context */
	pf_ctx_t  pr_ctx;

#ifdef PF_THREAD_SAFE
	pthread_mutex_t  pr_mtx;
#endif
	int     (*pr_init)(void);
	void    (*pr_fini)(void);
	int     (*pr_input)(PF_PKT *);
	int     (*pr_output)(PF_PKT *);
	int     (*pr_ctl)(int, void *);
};




static int pf_proto_autoload = 0;

/* family table */
struct pf_prfamily {
	char              pf_name[PFPR_FAM_MAXNAMELEN];
	int               pf_flags;

	struct pf_proto **pf_prswtab;		/* protocol switch table */
	int               pf_prswtabsz;		/* size of table */

#ifdef PF_THREAD_SAFE
	pthread_mutex_t   pf_mtx;
#endif
} pf_famtab[PFPR_PF_MAX + 1];



static struct pf_proto*  pf_proto_alloc  (void);



/*
 * pf_proto_init()
 *
 * Initialize the protocol entry table.
 */
int
pf_proto_init(void)
{
	int i;

	memset(pf_famtab, 0, sizeof(pf_famtab));

#ifdef PF_THREAD_SAFE
	for (i = 0; i <= PFPR_PF_MAX; i++)
		if (pthread_mutex_init(&pf_famtab[i].pf_mtx, NULL) != 0) {
			return (-1);
		}
#endif

	return (0);
}


/*
 * pf_proto_load()
 *
 * Register a protocol in the protocol table.  The protocol is assigned the
 * first available entry in the protocol hooks table, and its ID becomes the
 * index for that entry.
 * Returns the protocol ID on success, or -1 on failure.
 */
int
pf_proto_load(const struct pf_proto_desc *prdesc)
{
	int i, fam, id;
	void *tmp;
	struct pf_proto *pr;

	if (prdesc->pr_id < 0) {
		return (-1);
	}
	fam = PFPROTO_FAMILY(prdesc->pr_id);
	if (fam > PFPR_PF_MAX) {
		pf_seterr(PF_ERR_INVPRFAM);
		return (-1);
	}
	id = PFPROTO_ID(prdesc->pr_id);

	pf_log(pforge_chan, PFLOG_DEBUG, "loading protocol %s (family %d, id %d)",
	    prdesc->pr_name, fam, id);

#ifdef PF_THREAD_SAFE
	if (pthread_mutex_lock(&pf_famtab[fam].pf_mtx) != 0) {
		return (-1);
	}
#endif

	if (pf_famtab[fam].pf_prswtabsz <= (int)id) {
		pf_log(pforge_chan, PFLOG_DEBUG, "reallocating switch table for family %d",
		    fam);
		/*
		 * Reallocate the family's switch table to a size large
		 * enough so we can fit the new protocol
		 */
		i = pf_famtab[fam].pf_prswtabsz;
		if ((tmp = pf_realloc(pf_famtab[fam].pf_prswtab,
		    (id + PF_PROTO_GROWSIZE) * sizeof(PF_PROTO *))) == NULL) {
			pf_seterr(PF_ERR_NOMEM);
#ifdef PF_THREAD_SAFE
			(void)pthread_mutex_unlock(&pf_famtab[fam].pf_mtx);
#endif
			return (PFPR_INVAL);
		}
		pf_famtab[fam].pf_prswtab = (struct pf_proto **)tmp;
		/* NULLify the unused slots */
		while (i < id + PF_PROTO_GROWSIZE)
			pf_famtab[fam].pf_prswtab[i++] = NULL;
		pf_famtab[fam].pf_prswtabsz = id + PF_PROTO_GROWSIZE;
	}

	if ((pr = pf_proto_alloc()) == NULL) {
#ifdef PF_THREAD_SAFE
		(void)pthread_mutex_unlock(&pf_famtab[fam].pf_mtx);
#endif
		return (PFPR_INVAL);
	}

	pf_ctx_get(&pr->pr_ctx);
	strlcpy(pr->pr_name, prdesc->pr_name, sizeof(pr->pr_name));
	pr->pr_init = prdesc->pr_init;
	pr->pr_fini = prdesc->pr_fini;
	pr->pr_input = prdesc->pr_input;
	pr->pr_output = prdesc->pr_output;

	pf_log(pforge_chan, PFLOG_DEBUG, "registering protocol %s", pr->pr_name);
	pf_famtab[fam].pf_prswtab[id] = pr;

#ifdef PF_THREAD_SAFE
	(void)pthread_mutex_unlock(&pf_famtab[fam].pf_mtx);
#endif

	return (0);
}


int
pf_proto_unload(int prid)
{

	return (0);
}

/*
 * pf_proto_get()
 *
 * Get a protocol structure for the protocol with ID <protoid>.
 */
PF_PROTO*
pf_proto_get(int protoid)
{
	int fid, sid;
	PF_PROTO *pr;

	sid = PFPROTO_ID(protoid);

	if (((fid = PFPROTO_FAMILY(protoid)) < 0) || (fid > (int)PFPR_PF_MAX)) {
		pf_seterr(PF_ERR_INVPRFAM);
		return (NULL);
	}

#ifdef PF_THREAD_SAFE
	if (pthread_mutex_lock(&pf_famtab[fid].pf_mtx) != 0) {
		return (NULL);
	}
#endif

	pr = NULL;

	if ((pf_famtab[fid].pf_prswtabsz <= (int)sid) ||
	    (pf_famtab[fid].pf_prswtab[sid] == NULL)) {
		if (pf_proto_autoload) {
			pf_log(pforge_chan, PFLOG_DEBUG,
			    "attempting autoload for protocol %d", protoid);
		}
	} else
		pr = pf_famtab[fid].pf_prswtab[sid];
#ifdef PF_THREAD_SAFE
	(void)pthread_mutex_unlock(&pf_famtab[fid].pf_mtx);
#endif

	if (pr == NULL)
		pf_seterr(PF_ERR_NOENT);

	return (pr);
}


/*
 * pf_proto_getbyname()
 *
 * Get a protocol ID from the protocol's name string, pointed to by <name>.
 * Comparison is case-insensitive.
 * Returns a protocol ID on success, or XXX on error.
 */
int
pf_proto_getbyname(const char *name)
{
	int i = 0;

	return (-1);
}

/*
 * pf_proto_input()
 *
 * Generic protocol input function.  The <prid> argument specifies which
 * protocol should be called next for the packet <pkt>.
 */
int
pf_proto_input(int prid, PF_PKT *pkt)
{
	int ret;
	pf_ctx_t octx;
	PF_PROTO *pr;

	if ((pr = pf_proto_get(prid)) == NULL)
		return (-1);

	pf_log(pforge_chan, PFLOG_DEBUG, "calling protocol input handler");
	pf_ctx_set(pr->pr_ctx, &octx);
	ret = pr->pr_input(pkt);
	pf_ctx_set(octx, NULL);

	return (ret);
}

/*
int
pf_proto_output(int prid, PF_PKT *pkt, void *data)
{


}
*/

/*
 * pf_proto_alloc()
 *
 * Allocate a protocol structure and initialize its contents.
 * Returns a pointer to the structure on success, or NULL on failure.
 */
static struct pf_proto*
pf_proto_alloc(void)
{
	struct pf_proto *pr;

	if ((pr = (struct pf_proto *)pf_malloc(sizeof(*pr))) == NULL) {
		pf_seterr(PF_ERR_NOMEM);
		return (NULL);
	}
	memset(pr, 0, sizeof(*pr));

#ifdef PF_THREAD_SAFE
	if (pthread_mutex_init(&(pr->pr_mtx), NULL) != 0) {
		/* XXX set error */
		return (NULL);
	}
#endif

	return (pr);
}
